#pragma once

#include <iostream>
#include <istream>
#include <fstream>
#include <cstdlib>
#include <cassert>

// map a function (argument 1) to an array (argument 2)
template<class R, class A>
I_ARRAY<R> map(R (*func)(A), I_ARRAY<A> arr)
{
	// create return array
	I_ARRAY<R> ret_arr; ret_arr.size = arr.size;
	ret_arr.array = malloc(ret_arr.size * sizeof(R));
	
	// map the new values to the return array
	unsigned int i = arr.size;
	while (i--) ret_arr.array[i] = func(arr.array[i]);
	
	// return the array
	return ret_arr;
}

// get (sorted) array data from file
template<class T> I_ARRAY<T> load_array(C_STR filename)
{
	unsigned int s = 0;
	
	std::ifstream ifs(filename);
	trace_assert(ifs.is_open(), "FAILED to load array input file. File not found or I/O error.");
	
	// get booking data
	I_ARRAY<T> a; ifs >> a.size;
	a.arr = (T*) malloc(a.size * sizeof(T));
	
	// while we still have keys in the file
	while (s < a.size && ifs >> a.arr[s++]);
	
	// close the stream
	ifs.close();
	
	// trace success
	char lenmsg[C_STR_SIZE]; sprintf(lenmsg, "%d", s);
	trace(TRACE_LEVEL_DEBUG, "SUCCESSFULLY loaded array from file '", filename, "'. ", lenmsg, " entries loaded.");
	
	return a;
};

// free I_ARRAY data structure
template<class T> void free_array(I_ARRAY<T> a)
{ free(a.arr); };

// get position boards from file
void load_position_boards(I_POSITION_BOARDS *b, C_STR filename)
{
	// input file stream
	std::ifstream ifs(filename);
	
	// load position board
	load_position_boards(b, ifs);
	
	// close file stream
	ifs.close();
};

// get position boards from input stream
void load_position_boards(I_POSITION_BOARDS *b, std::istream& ifs)
{
	// size of I_POSITION_BOARDS array
	int const SIZE = I_PIECE_COUNT;
	
	// variables and iterators
	int s, iA, iB, j, tmp;
	
	// setup array
	memcpy(b, &BLANK_POSITION_BOARD, SIZE * sizeof(I_BOARD_1D));
	
	// while we still have board data in the file
	for (s = 0; s < SIZE; s += PLAYERS)
	{
		// read in an entire board
		for (iA = I_BOARD_BEGIN, iB = I_BOARD_RBEGIN; iA < I_BOARD_END; ++iA, --iB)
		for (j  = I_BOARD_BEGIN;                      j  < I_BOARD_END; ++j)
		{
			// read this value from file...
			ifs >> tmp;
			
			// ...and into the boards.
			(*b)[s + I_SIDE_WHITE][iA * I_BOARD_RANK + j] = tmp;
			(*b)[s + I_SIDE_BLACK][iB * I_BOARD_RANK + j] = tmp;
		};
	};
	
	// trace success
	trace(TRACE_LEVEL_DEBUG, "SUCCESSFULLY loaded position boards.");
};

// binary search algorithm (for I_ARRAY)
template<class T> int binary_search(I_ARRAY<T> a, T z)
{ return binary_search<T>(a.arr, a.size, z); };

// binary search algorithm (for dynamic array)
template<class T> int binary_search(T* a, int s, T z)
{
	// midpoint
	int m = s >> 1;
	
	// if we found the object
	if (a[m] == z) return m;
	
	// if the scope is too narrow
	if (!m) return -1;
	
	// otherwise, narrow the search
	return a[m] > z ? binary_search<T>(a, m, z) : binary_search<T>(a + m, s - m, z);
};

// linear search algorithm (for I_ARRAY)
template<class T> int linear_search(I_ARRAY<T> a, T e)
{ return linear_search<T>(a.arr, a.size, e); };

// linear search algorithm (for dynamic array)
template<class T> int linear_search(T* arr, int s, T e)
{ while (s) if (arr[--s] == e) return s; return -1; };

// read a game board in Forsyth-Edwards notation
I_CHESS_POSITION* read_FEN(C_STR fen)
{
	// trace beginning
	trace(TRACE_LEVEL_DEBUG, "BEGAN parsing Forsyth-Edwards Notation.");
	
	// const failure message
	CONST_C_STR failure_msg = "FAILED to load FEN. Invalid Forsyth-Edwards Notation, returning NULL.";
	
	// pointer to the chess board object
	I_CHESS_POSITION* tmp = empty_board();
	
	// copy blank board into the game board
	memcpy(tmp->brd, &BLANK_BOARD, sizeof(int) * I_BOARD_RANK * I_BOARD_RANK);
	
	// iterators and variables
	int i = I_BOARD_BEGIN * I_BOARD_RANK + I_BOARD_BEGIN;
	int piece; unsigned char fchar, cp, *bp_brd, *bp_cst;
	
	// buffers and buffer sizes
	int const BOARD_BUFFER_SIZE = 72; unsigned char bbuff[BOARD_BUFFER_SIZE];
	int const CASTL_BUFFER_SIZE = 5;  unsigned char cbuff[CASTL_BUFFER_SIZE];
	int const ENPSS_BUFFER_SIZE = 3;  unsigned char ebuff[ENPSS_BUFFER_SIZE];
	
	// get the format (hope for the best)
	sscanf(fen, "%s %c %s %s %d %d", bbuff, &cp, cbuff, ebuff, &tmp->halfmoves, &tmp->fullmoves);
	
	// setup pointers
	bp_brd = bbuff;
	bp_cst = cbuff;
	
	// parse board representation
	while (*bp_brd)
	{
		// get current char
		fchar = *(bp_brd++);
		
		// digits represent the number of blank spaces
		if (isdigit(fchar)){ i += (fchar - '0'); continue; };
		
		// if '/', goto the next rank of the board
		if (fchar == '/'){ i += 4; continue; };
		
		// anything else, (we can assume) is a piece
		if ((piece = linear_search<char>((C_STR)PIECE_CHARS, PIECE_CHARS_SIZE, fchar)) != -1)
		{
			// if it is a pawn, add to pawn count
			if (is_piece_type(piece, I_PIECE_PA)) ++tmp->pwn[piece & 1];
			
			// if it is the king, update king location
			if (is_piece_type(piece, I_PIECE_KI)) tmp->king[piece & 1] = i;
			
			// add piece to the board and move along...
			tmp->brd[i++] = piece; continue;
		};
		
		// if we see anything else, something is wrong
		trace(TRACE_LEVEL_WARN, failure_msg);
		free(tmp); return NULL;
	};
	
	// set current player
	switch (cp)
	{
		case 'w': tmp->col = I_SIDE_WHITE; break;
		case 'b': tmp->col = I_SIDE_BLACK; break;
		
		default:
			trace(TRACE_LEVEL_WARN, failure_msg);
			free(tmp); return NULL; break;
	};
	
	// set castling flags
	while (*bp_cst) switch (*(bp_cst++))
	{
		case 'K': tmp->cst ^= (1 << (I_CASTLE_KI + I_SIDE_WHITE)); break;
		case 'k': tmp->cst ^= (1 << (I_CASTLE_KI + I_SIDE_BLACK)); break;
		case 'Q': tmp->cst ^= (1 << (I_CASTLE_QU + I_SIDE_WHITE)); break;
		case 'q': tmp->cst ^= (1 << (I_CASTLE_QU + I_SIDE_BLACK)); break;
		case '-': if (tmp->cst == NO_CASTLING) break;
		
		default:
			trace(TRACE_LEVEL_WARN, failure_msg);
			free(tmp); return NULL; break;
	};
	
	// en passant
	if (*ebuff == '-') tmp->enp = 0;
	else tmp->enp = get_index((C_STR)ebuff);
	
	// return the generated board
	trace(TRACE_LEVEL_DEBUG, "SUCCESSFULLY generated board from Forsyth-Edwards Notation.");
	return tmp;
};

C_STR write_FEN(I_CHESS_POSITION *p)
{
	// create our return buffer
	C_STR fen_buffer = (C_STR) malloc(FEN_BUFFER_SIZE * sizeof(char));
	
	// temporary board string buffer
	C_STR brd_buffer = (C_STR) malloc(FEN_BRD_BUF_SIZE * sizeof(char));
	
	// iterators and variables
	int i, j, k = 0, piece, s;
	
	// create the board string
	for (i = I_BOARD_BEGIN; i < I_BOARD_END; ++i)
	{
		// reset the spacing value
		s = 0;
		
		// for each file in this rank
		for (j = I_BOARD_BEGIN; j < I_BOARD_END; ++j)
		{
			// piece at this location on the board
			piece = p->brd[i * I_BOARD_RANK + j];
			
			// if empty square, add to spaces
			if (piece == I_BOARD_EMPTY) ++s;
			else 
			{
				// if there are spaces, print them
				if (s) brd_buffer[k++] = s + '0'; s = 0; 
				
				// print the current piece
				brd_buffer[k++] = PIECE_CHARS[piece];
			};
		};
		
		// if there are spaces, print the value
		if (s) brd_buffer[k++] = s + '0';
		
		// write out the line delimiter
		brd_buffer[k++] = '/';
	}
	
	// change the last delimiter to a null terminator
	brd_buffer[--k] = (char)0;
	
	// other characters/buffers
	unsigned char curplayer;
	
	int const CASTL_BUFFER_SIZE = 5; unsigned char cbuff[CASTL_BUFFER_SIZE];
	int const ENPSS_BUFFER_SIZE = 3; unsigned char ebuff[ENPSS_BUFFER_SIZE];
	
	// set the character correctly
	switch (p->col)
	{
		case I_SIDE_WHITE: curplayer = 'w'; break;
		case I_SIDE_BLACK: curplayer = 'b'; break;
		
		default:
			trace(TRACE_LEVEL_WARN, "FAILED to write Forsyth-Edwards Notation, returning NULL.");
			free(fen_buffer); return NULL; break;
	};
	
	// if no castling is legal, just print a dash
	if (p->cst == NO_CASTLING) strcpy((C_STR)cbuff, "-");
	else
	{
		// reset the iterator
		k = 0;
		
		// enter castling flags based on what is legal
		if (p->cst & (1 << (I_CASTLE_KI + I_SIDE_WHITE))) cbuff[k++] = 'K';
		if (p->cst & (1 << (I_CASTLE_KI + I_SIDE_BLACK))) cbuff[k++] = 'k';
		if (p->cst & (1 << (I_CASTLE_QU + I_SIDE_WHITE))) cbuff[k++] = 'Q';
		if (p->cst & (1 << (I_CASTLE_QU + I_SIDE_BLACK))) cbuff[k++] = 'q';
		
		// add the null terminator to the end
		cbuff[k] = (char)0;
	};
	
	// if there is no en passant pawn, print a dash
	if (p->enp == NO_EN_PASSANT) strcpy((C_STR)ebuff, "-");
	else
	{
		// reset the iterator
		k = 0;
		
		// give the location of the en passant pawn
		ebuff[k++] = (p->enp % I_BOARD_RANK) - I_BOARD_BEGIN  + 'a';
		ebuff[k++] = I_BOARD_RBEGIN - (p->enp / I_BOARD_RANK) + '1';
		
		// add the null terminator to the end
		ebuff[k] = (char)0;
	};
	
	// print the string into the buffer
	sprintf(fen_buffer, "%s %c %s %s %d %d", brd_buffer, curplayer, cbuff, ebuff, p->halfmoves, p->fullmoves);
	
	// free the board buffer
	free(brd_buffer);
	
	// return the Forsyth-Edwards Notation
	return fen_buffer;
};

// convert "a1" style coordinates to 144-square coordinates
inline int get_index(char *ptr)
{ return rank(ptr[1]) * I_BOARD_RANK + file(ptr[0]); };

inline int rank(char num)
{ return I_BOARD_RBEGIN - (num - '1'); };

inline int file(char ltr)
{ return I_BOARD_BEGIN + (ltr - 'a'); };

// convert 144-square coordinates to "a1" style coordinates
inline void write_coord(int k, C_STR ptr)
{ write_file(k, ptr); write_rank(k, ptr + 1); };

inline void write_rank(int k, C_STR ptr)
{ *ptr = '1' + (I_BOARD_RBEGIN - (k / I_BOARD_RANK)); };

inline void write_file(int k, C_STR ptr)
{ *ptr = 'a' + ((k % I_BOARD_RANK) - I_BOARD_BEGIN); };
